package tr.helper.repository;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import tr.helper.CommonUtil;
import tr.helper.domain.DataConfig;
import tr.helper.repository.annotation.DbFiled;
import tr.helper.repository.annotation.Table;
import tr.helper.repository.table.Config;

/**
 * tr-service
 *
 * @author MrYe
 * @date 2022-01-14 11:37
 */
public class DataInitialization {

    public void initSchema(boolean drop) throws Exception {
        final String pkg = "tr.helper.repository.table";
        List<String> packageNames = new ArrayList<>();
        scanPackage(pkg, packageNames);
        var classMap = createNewInstances(packageNames);
        if (drop) {
            dropSchema(classMap);
        }
        generateSchema(classMap);
    }

    public Connection createConnection() {
        try {

            Class.forName("org.sqlite.JDBC");

            URL url = getClass().getClassLoader().getResource(DataConfig.DB);
            if (url != null) {
                URI uri = url.toURI();
                if (uri.getScheme().equals("jar")) {
                    FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
                    Path filePathInJar = fileSystem.getPath("/" + DataConfig.DB);
                    Path tempFilePath = Paths.get(System.getProperty("user.dir") + "/temp/" + DataConfig.DB);
                    System.out.println("tempFilePath=" + tempFilePath);
                    if (!Files.exists(tempFilePath)) {
                        FileUtil.touch(tempFilePath.toFile());
                        System.out.println("touch tempFilePath=" + tempFilePath);
                        Files.copy(filePathInJar, tempFilePath, StandardCopyOption.REPLACE_EXISTING);
                        System.out.println("File copied successfully.");
                    } else {
                        System.out.println("Temp file already exists. Skipping copy operation.");
                    }
                    fileSystem.close();

                    // 在这里使用临时文件路径 tempFilePath 来访问数据库
                    return DriverManager.getConnection("jdbc:sqlite:" + tempFilePath.toString());
                } else {
                    return DriverManager.getConnection("jdbc:sqlite:" + url.getPath());
                }
            } else {
                return DriverManager.getConnection("jdbc:sqlite:" + getClass().getResource("/" + DataConfig.DB).getPath());
            }

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    private Map<String, Class<?>> createNewInstances(List<String> packageNames) throws Exception {
        if (packageNames.size() <= 0) {
            return new HashMap<>();
        }
        final Map<String, Class<?>> classMap = new HashMap<>();
        for (String className : packageNames) {
            Class<?> cName = Class.forName(className.replace(".class", "").trim());
            if (cName.isAnnotationPresent(Table.class)) {
                classMap.put(cName.getSimpleName(), cName);
            }
        }
        return classMap;
    }

    private void scanPackage(String packagePath, List<String> packageNames) {
        // 将所有的.转义获取对应的路径
        final String pkgPath = StringUtils.replace(packagePath, ".", File.separator);
        URL url = Config.class.getClassLoader().getResource("");
        if (null != url) {
            String pathFile = url.getFile();
            pathFile = StringUtils.replace(pathFile, "file:", "");
            CommonUtil.log("new pathFile=%s", pathFile);
            // file:/Users/yeweidong/Documents/java/work/tr-service/target/jfx/app/tr-1.0-SNAPSHOT-jfx.jar!/com/tr/helper/repository/table
            List<String> files = FileUtil.listFileNames(pathFile);
            if (CollectionUtil.isNotEmpty(files)) {
                for (String path : files) {
                    CommonUtil.log("FileList path=%s", path);
                    //File eachFile = new File(pathFile + "/" + path);
                    if (FileUtil.isDirectory(pathFile + File.separator + path)) {
                        scanPackage(packagePath + "." + path, packageNames);
                    } else {
                        if (StringUtils.endsWith(path, "class")) {
                            packageNames.add(packagePath + "." + path);
                        }
                    }
                }
            }
        }
    }

    /**
     * mvn jfx:native
     * final Statement stmt1 = connection.createStatement();
     * stmt1.execute("alter table user add column cookie varchar(200) null");
     * stmt1.close();
     *
     * @param classMap 对象class
     */
    public void generateSchema(Map<String, Class<?>> classMap) {
        try {
            final Connection connection = createConnection();
            CommonUtil.log("Opened database successfully");

            for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {

                final Statement stmt = connection.createStatement();

                String tableName = entry.getKey();
                final Table table = entry.getValue().getAnnotation(Table.class);
                if (StringUtils.isNotBlank(table.value())) {
                    tableName = table.value();
                }

                StringBuilder sqlBuilder = new StringBuilder(String.format("CREATE TABLE IF NOT EXISTS %s (", tableName));

                final Field[] fields = entry.getValue().getDeclaredFields();
                for (Field field : fields) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    DbFiled dbFiled = field.getAnnotation(DbFiled.class);
                    String fieldName = field.getName();
                    String type = field.getGenericType().toString();

                    if (null != dbFiled && StringUtils.isNotBlank(dbFiled.value())) {
                        fieldName = dbFiled.value();
                    }

                    Object dbFiledType = Support.mysqlMapping.get(type);
                    if (null == dbFiledType) {
                        CommonUtil.log("不支持的类型:%s", type);
                        continue;
                    }
                    String sqlDbType = StringUtils.contains(dbFiledType.toString(), "(") ? dbFiledType.toString()
                        : dbFiledType.toString() + String.format("(%s)", Support.getLength(dbFiled));
                    if (dbFiled != null && dbFiled.primary()) {
                        sqlBuilder.append(String.format("%s %s PRIMARY KEY %s,", fieldName, sqlDbType, Support.getIsNull(dbFiled)));
                    } else {
                        sqlBuilder.append(String.format("%s %s %s,", fieldName, sqlDbType, Support.getIsNull(dbFiled)));
                    }
                }
                String sql = sqlBuilder.toString();
                sql = CommonUtil.trimEnd(sql, ",") + ")";

                final boolean suc = stmt.execute(sql);
                CommonUtil.log("tableName:%s suc:%s sql:%s", tableName, suc, sql);
                stmt.close();
            }
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
        CommonUtil.log("Table created successfully");
    }

    private void dropSchema(Map<String, Class<?>> classMap) {
        try {
            Class.forName("org.sqlite.JDBC");
            final Connection connection = DriverManager.getConnection("jdbc:sqlite:" + DataConfig.DB);

            for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
                final Statement stmt = connection.createStatement();

                String tableName = entry.getKey();
                final Table table = entry.getValue().getAnnotation(Table.class);
                if (StringUtils.isNotBlank(table.value())) {
                    tableName = table.value();
                }

                final String sql = String.format("DROP TABLE IF EXISTS %s", tableName);
                final boolean dropFlag = stmt.execute(sql);
                stmt.close();
                CommonUtil.log("execute %s sql:%s", dropFlag, sql);
            }

            connection.close();
        } catch (Exception e) {
            CommonUtil.log((e.getClass().getName() + ": " + e.getMessage()));
            System.exit(0);
        }
        CommonUtil.log("Table drop successfully");
    }

}
